#ifndef __VECTOR_2_HPP__
#define __VECTOR_2_HPP__

#define USE_NLOHMANN_JSON 1
#define USE_FMT_TO_STRING 1

#include <stdint.h>
#include <assert.h>
#include <cmath>
#include "Point2.hpp"
#include "MathUtils.hpp"

#if USE_NLOHMANN_JSON
#include "nlohmann/json.hpp"
#endif

#if USE_FMT_TO_STRING
#include "spdlog/fmt/fmt.h"
#endif
namespace PocketSurvival::Engine
{
    struct Vector2
    {
        float posX;
        float posY;

        constexpr Vector2() noexcept;
        constexpr Vector2(const Point2 &point) noexcept;
        constexpr Vector2(float x) noexcept;
        constexpr Vector2(float x, float y) noexcept;

        constexpr inline Vector2 &operator+=(const Vector2 &left);
        constexpr inline Vector2 &operator-=(const Vector2 &left);
        constexpr inline Vector2 &operator*=(float c);
        constexpr inline Vector2 &operator/=(float c);

        constexpr inline Point2 toPoint2();
        constexpr inline float volume() const;
        constexpr inline float lengthSquared() const;
        inline float length() const;

        static inline float DistanceSquared(const Vector2 &v1, const Vector2 &v2)
		{
			return MathUtils::Sqr<float>(v1.posX - v2.posX) + MathUtils::Sqr<float>(v1.posY - v2.posY);
		}
        static inline float Distance(const Vector2 &v1, const Vector2 &v2)
		{
			return MathUtils::Sqrt(DistanceSquared(v1, v2));
		}


        friend constexpr inline bool operator==(const Vector2 &left, const Vector2 &right)
        {
            return (left.posX == right.posX) && (left.posY == right.posY);
        }
        friend constexpr inline bool operator!=(const Vector2 &left, const Vector2 &right)
        {
            return !(left == right);
        }
        friend constexpr inline Vector2 operator+(const Vector2 &left, const Vector2 &right)
        {
            return Vector2(left.posX + right.posX, left.posY + right.posY);
        }
        friend constexpr inline Vector2 operator-(const Vector2 &left, const Vector2 &right)
        {
            return Vector2(left.posX - right.posX, left.posY - right.posY);
        }
        friend constexpr inline Vector2 operator*(const Vector2 &left, const Vector2 &right)
        {
            return Vector2(left.posX * right.posX, left.posY * right.posY);
        }

        friend constexpr inline Vector2 operator*(const Vector2 &left, float c)
        {
            return Vector2(left.posX * c, left.posY * c);
        }
        friend constexpr inline Vector2 operator*(float c, const Vector2 &right)
        {
            return Vector2(right.posX * c, right.posY * c);
        }
        friend constexpr inline Vector2 operator+(const Vector2 &left)
        {
            return left;
        }
        friend constexpr inline Vector2 operator-(const Vector2 &left)
        {
            return Vector2(-left.posX, -left.posY);
        }
        friend constexpr inline Vector2 operator/(const Vector2 &left, float divisor)
        {
            assert(divisor < 0.0f || divisor > 0.0f);
            return Vector2(left.posX / divisor, left.posY / divisor);
        }
        friend constexpr inline Vector2 operator/(const Vector2 &left, const Vector2 &right)
        {
            assert(!(right.posX == 0.0f || right.posY == 0.0f));
            return Vector2(left.posX / right.posX , left.posY / right.posY);
        }

        static inline Vector2 Min(const Vector2& vec1, const Vector2 &vec2)
        {
            return Vector2(MathUtils::Min<float>(vec1.posX, vec2.posX), MathUtils::Min<float>(vec1.posY, vec2.posY));
        }
        static inline Vector2 Max(const Vector2& vec1, const Vector2 &vec2)
        {
            return Vector2(MathUtils::Max<float>(vec1.posX, vec2.posX), MathUtils::Max<float>(vec1.posY, vec2.posY));
        }
        static inline float Cross(const Vector2 &vec1, const Vector2 &vec2)
        {
            return vec1.posX * vec2.posY - vec1.posY * vec2.posX;
        }
        static inline float Dot(const Vector2 &vec1, const Vector2 &vec2)
        {
            return (vec1.posX * vec2.posX) + (vec1.posY * vec2.posY);
        }
        static inline Vector2 Normalize(const Vector2 &vec)
        {
            float num = vec.length();
            if((num > 0.0f) == false)
            {
                return Vector2{1.0f, 0.0f};
            }
            return vec / num;
        }
		static inline Vector2 Lerp(const Vector2 &v1, const Vector2 &v2, float f)
		{
			return Vector2(MathUtils::Lerp(v1.posX, v2.posX, f), MathUtils::Lerp(v1.posY, v2.posY, f));
		}
        static inline float Angle(const Vector2 &v1, const Vector2 &v2)
        {
            float num = MathUtils::Atan2(v1.posY, v1.posX);
            float num2 = MathUtils::Atan2(v2.posY, v2.posX) - num;
            if (num2 > (float)MathUtils::GetPI())
            {
                num2 -= (float)MathUtils::GetPI() * 2.0f;
            }
            else if (num2 <= -(float)MathUtils::GetPI())
            {
                num2 += (float)MathUtils::GetPI() * 2.0f;
            }
            return num2;
        }
        static inline Vector2 CreateFromAngle(float angle)
        {
            float y = MathUtils::Cos(angle);
            return Vector2(0.0f - MathUtils::Sin(angle), y);
        }


#if USE_NLOHMANN_JSON
        friend inline void to_json(nlohmann::json &json, const Vector2 &vec);
        friend inline void from_json(const nlohmann::json &json, Vector2 &vec);
#endif

#if USE_FMT_TO_STRING
        inline std::string toString() const
        {
            return fmt::format("{},{}",posX,posY);
        }
#endif
    };

    constexpr inline Vector2::Vector2() noexcept : posX(0), posY(0) {}

    constexpr inline Vector2::Vector2(float x) noexcept : posX(x), posY(x) {}

    constexpr inline Vector2::Vector2(float x, float y) noexcept : posX(x), posY(y) {}

    constexpr inline Vector2::Vector2(const Point2 &point) noexcept : posX(point.posX), posY(point.posY) {}

    constexpr inline Vector2 &Vector2::operator+=(const Vector2 &right)
    {
        posX += right.posX;
        posY += right.posY;
        return *this;
    }

    constexpr inline Vector2 &Vector2::operator-=(const Vector2 &right)
    {
        posX -= right.posX;
        posY -= right.posY;
        return *this;
    }

    constexpr inline Vector2 &Vector2::operator*=(float c)
    {
        posX *= c;
        posY *= c;
        return *this;
    }

    constexpr inline Vector2 &Vector2::operator/=(float divisor)
    {
        assert(divisor > 0 || divisor < 0);
        posX /= divisor;
        posY /= divisor;
        return *this;
    }

    constexpr inline Point2 Vector2::toPoint2()
    {
        return Point2((int32_t)posX, (int32_t)posY);
    }
    constexpr inline float Vector2::volume() const
    {
        return posX * posY;
    }
    constexpr inline float Vector2::lengthSquared() const
    {
        return posX * posX + posY * posY;
    }
    inline float Vector2::length() const
    {
        return MathUtils::Sqrt<float>(lengthSquared());
    }

#if USE_NLOHMANN_JSON
    inline void to_json(nlohmann::json &json, const Vector2 &vec)
    {
        float xValue = std::isnan(vec.posX) ? 0 : vec.posX;
        float yValue = std::isnan(vec.posY) ? 0 : vec.posY;

        json = {{"X",xValue},{"Y",yValue}};
    }
    inline void from_json(const nlohmann::json &json, Vector2 &vec)
    {
        vec.posX = json["X"].get<float>();
        vec.posY = json["Y"].get<float>();
    }
#endif

    namespace FastVector2
    {
        constexpr inline Vector2 Zero{0.0f};
        constexpr inline Vector2 One{1.0f};
        constexpr inline Vector2 UnitX{1.0f, 0.0f};
        constexpr inline Vector2 UnitY{0.0f, 1.0f};
    }

}

#undef USE_NLOHMANN_JSON
#undef USE_FMT_TO_STRING

#endif
